﻿using System;

namespace BizElements.Core
{
    /// <summary>
    /// Identifies primary table in a many-to-many relation.
    /// <seealso cref="ManyToManyRelation"/>
    /// </summary>
    public enum PrimaryTableIndex
    {
        #region Values.

        /// <summary>
        /// Identifies the first primary table in an M:N relation.
        /// </summary>
        FirstPrimaryTable,

        /// <summary>
        /// Identifies the second primary table in an M:N relation.
        /// </summary>
        SecondPrimaryTable

        #endregion
    }

    #region Documentation.
    /// <summary>
    /// Models a many-to-many relation using two one-to-many relations through a junction table.
    /// <seealso cref="IEntityDAO"/><seealso cref="PrimaryTableIndex"/>
    /// </summary>
    /// <remarks>In a many-to-many (M:N) relationship, a row in one table can have many matching rows in antoher table, 
    /// and vice versa. These two tables are primary tables of a many-to-many relation. An M:N relationship between 
    /// two primary tables is created by using a third table, called a junction table, whose primary key consists of 
    /// the foreign keys from both primary tables.</remarks>
    /// <example>
    /// The following example fetches children entities using a many-to-many relation.
    /// <code>
    /// public DataTable FetchEmployeeTerritories(EmployeesEntity employee)
    /// {			
    /// 	// Fetch territories that the given employee is in charge of.
    /// 
    /// 	// An employee can be in charge of multiple territories.
    /// 	// Multiple employees can be in charge of a single territory.
    /// 	// Employees and Territories table are related by creating junction rows in EmployeeTerritories table.
    /// 	// EmployeeTerritories table has a primary key which is composed of two foreign keys.
    /// 	// These two one-to-many (1:N) relations can be modeled as a one many-to-many (M:N) relation.
    /// 
    /// 	EmployeeTerritoriesMeta junctionTable = new EmployeeTerritoriesMeta();
    /// 	ManyToManyRelation mnRelation = new ManyToManyRelation(junctionTable.FK_EmployeeID, junctionTable.FK_TerritoryID);
    /// 
    /// 	DataTable territories = employee.GetChildren(mnRelation);
    /// 	return territories;
    /// }
    /// </code>
    /// </example>
    #endregion
    [Serializable]
    public sealed class ManyToManyRelation
    {
        #region Members.

        DbRelation firstTableToJunction;
        DbRelation secondTableToJunction;
        SearchCondition junctionFilter;

        #endregion

        #region Constructors.

        /// <summary>
        /// Initializes a new instance of the ManyToManyRelation class.
        /// </summary>
        /// <param name="firstTableToJunction">Relation between the junction table and the first primary table.</param>
        /// <param name="secondTableToJunction">Relation between the junction table and the second primary table.</param>
        public ManyToManyRelation(DbRelation firstTableToJunction, DbRelation secondTableToJunction)
        {
            if (!firstTableToJunction.Child.HasEqualAliasAndNameAs(secondTableToJunction.Child))
                throw new InvalidOperationException(Messages.ManyToManyRelation_RelationsSpecifyDiffJunctionTables);

            this.firstTableToJunction = firstTableToJunction;
            this.secondTableToJunction = secondTableToJunction;
        }

        #endregion

        #region Properties.

        /// <summary>
        /// Gets the first primary table of the M:N relation.
        /// </summary>
        public IDbTable FirstPrimaryTable
        {
            get { return this.firstTableToJunction.Parent; }
        }

        /// <summary>
        /// Gets the relation between the junction table and the first primary table.
        /// </summary>
        public DbRelation FirstTableToJunction
        {
            get { return this.firstTableToJunction; }
        }

        /// <summary>
        /// Gets the second primary table of the M:N relation.
        /// </summary>
        public IDbTable SecondPrimaryTable
        {
            get { return this.secondTableToJunction.Parent; }
        }

        /// <summary>
        /// Gets the relation between the junction table and the second primary table.
        /// </summary>
        public DbRelation SecondTableToJunction
        {
            get { return this.secondTableToJunction; }
        }

        /// <summary>
        /// Gets the junction table of the M:N relation.
        /// </summary>
        public IDbTable JunctionTable
        {
            get { return this.firstTableToJunction.Child; }
        }

        /// <summary>
        /// Gets a value indicating whether the M:N relation is recursive, i.e. if a single database table is used
        /// as both the first and the second primary table.
        /// </summary>
        public bool IsRecursive
        {
            get { return (this.firstTableToJunction.Parent.TableName == this.secondTableToJunction.Parent.TableName); }
        }

        /// <summary>
        /// Gets or sets the <see cref="SearchCondition"/> that filters out junction rows.
        /// Only junction table fields may be used in the filter.
        /// </summary>
        /// <remarks><p>Default value is <b>null</b>, which means that all junction rows represent a valid link between two tables.</p>
        /// <p>Database is often designed so that the junction rows are never deleted from the database but are marked 
        /// as deleted instead. The JunctionRowsFilter may be used to filter out the rows which are marked as inactive.</p>
        /// <p>Use only simple filters. The search condition may only reference table fields from the junction table; 
        /// otherwise an error will occur. Use <see cref="SelectStatement"/> when complex filtering is required.</p></remarks>
        /// <example>
        /// The following example creates an M:N relation and a JunctionRowsFilter that allows only junction rows
        /// whose STATUS field is set to 1.
        /// <code>
        /// EmployeeTerritoriesMeta junctionTable = new EmployeeTerritoriesMeta();
        /// ManyToManyRelation mnRelation = new ManyToManyRelation(junctionTable.FK_EmployeeID, junctionTable.FK_TerritoryID);
        /// mnRelation.JunctionRowsFilter = new SearchCondition(PredicateFactory.Compare(junctionTable.Status, "=", 1));
        /// </code>
        /// </example>
        public SearchCondition JunctionRowsFilter
        {
            get { return this.junctionFilter; }
            set { this.junctionFilter = value; }
        }

        #endregion
    }
}